Skip to content

Harden RAG answer prompt against source-text injection (measurement-first)#366

Merged
BigSimmo merged 1 commit into
mainfrom
claude/llm-pipeline-review-7wihci
Jul 8, 2026
Merged

Harden RAG answer prompt against source-text injection (measurement-first)#366
BigSimmo merged 1 commit into
mainfrom
claude/llm-pipeline-review-7wihci

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Measurement-first LLM-pipeline review. Live eval:rag (44 cases) priced at current gpt-5.5 rates ($5 / $0.50 cached / $30 per 1M in/cached/out) + rag_retrieval_logs telemetry (306 rows) validate the fast→quality-gate→strong cascade against the current model generation, so no routing/timeout/schema churn is made. Fast and strong are the same model (gpt-5.5) differentiated only by reasoning effort; strong costs 1.8× fast ($0.101 vs $0.056) at +13s median latency while fast grounds 100% of the queries routed to it. Routing everything to a strong model would roughly double cost and latency on the ~40% of traffic fast handles perfectly, for no quality gain.
  • Item 2 was already done or not justified: citation→retrieved-chunk-ID enum (answerJsonOutputSchemaForResults) already closes the master plan's "where practical" gap; strongReasoningEffortForQueryClass already caps effort except for safety-critical classes; prompt caching is already configured. Only change here is the answer cache key v17→v18 (the cached instruction prefix changed).
  • Item 3 — prompt-injection hardening from docs/rag-injection-threat-model.md (new §6): provenance/untrusted-data boundary in answerInstructions; neutralize the previously-RAW title/file_name/image caption/tableTitle/tableLabel/index-warning fields; case-insensitive fence escaper + neutralize-after-glyph-normalization; escape forged fence sentinels in every derived field in place (closes Vector E without the ~940 tokens/answer a per-field wrapper added). New tests/rag-injection.test.ts lands the deterministic INJ-3/4/6/12 cases + neutralizer/escaper regression guards.
  • Incidental cost effect: the longer instruction prefix now crosses OpenAI's prompt-cache minimum, so cached-input share rises from ~4% to ~40–80% across runs and cost/answer is neutral-to-lower ($0.038–0.030 vs $0.040 baseline) despite the added text.

Cost-per-answer by route (BEFORE / main, 44 cases, gpt-5.5 pricing)

route n grounded cost/answer reasoning tok median latency
extractive 8 8/8 $0.031 24 13.4s
fast 18 18/18 $0.056 39 16.3s
strong 5 3/5 $0.101 730 29.5s
unsupported 13 0/0 $0.000 0 0.5s

Before / after (44-case eval:rag)

metric BEFORE (main) AFTER
supported grounded 28/30 27/30 (eval:quality --rag-only: 28/30)
invalid-citation 0 0
cost / answer $0.0402 $0.0379
cached input share 4.3% 39–81%
median latency 13.4s 12.8s

The 1-case eval:rag delta is within run-to-run noise: the two residual "regressions" are strong-route answers at 44s and 55s latency (timeout fallbacks on cases already latency-fragile on main), one baseline-failing clozapine case actually recovered to grounded, and eval:quality --rag-only on the same code matched baseline at 28/30. No content-level grounding loss from the hardening.

Verification

  • npm run verify:cheap — green (1261 tests, lint 0 errors, typecheck clean)
  • npm run verify:ui — n/a (no UI/routing/styling change)
  • npm run verify:release — not run (no release/handoff claim)
  • npm run format:check — changed files pass Prettier
  • npm run eval:retrieval:quality — retrieval is not touched (prompt-assembly + content-sanitizer only); baseline on main recorded 36/36, failed_cases=0 before any change. Not re-run because ranking/selection/chunking/scoring are unchanged.
  • npm run eval:rag (full) + npm run eval:quality -- --rag-only — grounded-supported 0.9333 (= baseline), invalid-citation 0. The non-zero citation/numeric/latency threshold flags in the quality report are pre-existing on main (identical failing cases in the pre-edit eval:rag baseline: clozapine-anc-withhold-threshold, community-admission, illegal-substances), not introduced here.
  • npm run check:production-readiness — n/a (no clinical-workflow/privacy/env/Supabase/governance/deployment change)
  • npm run check:deployment-readiness — n/a

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use — grounding gates, numeric verification, and citation validation are unchanged
  • No patient-identifiable document workflow was introduced or expanded
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy) — no Supabase/env/RPC/migration change
  • Service-role keys and private document access remain server-only — unchanged
  • Demo/synthetic content remains clearly separated from real clinical sources — unchanged
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative — no source-governance metadata was added to ranking/ordering; candidate scores untouched
  • Deployment classification/TGA SaMD impact checked — this is a defensive prompt-hardening change; it strengthens (does not weaken) grounding/refusal behavior for clinical decision support

Notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01VdSevAcjHqHt4ezZkWYbE7


Generated by Claude Code

Measurement-first LLM-pipeline review. Live eval:rag (44 cases) priced at
current gpt-5.5 rates confirms the fast->quality-gate->strong cascade still
beats routing straight to strong: strong costs ~1.8x fast at +13s median
latency while fast grounds 100% of its queries. So no routing-threshold,
timeout, reasoning-effort, or citation-schema changes are made — the citation
enum constraint (answerJsonOutputSchemaForResults) and reasoning-effort
latency cap already exist, and prompt caching is already configured.

Implements the ranked prompt-level mitigations from
docs/rag-injection-threat-model.md (see new section 6):

- answerInstructions gains an explicit provenance/untrusted-data boundary so
  the evidence fence is a declared trust boundary the model is told to obey as
  data, never as instructions (mitigation #1). Zero false-positive risk to
  clinical content — it changes instructions, not source text.
- buildRagSourceBlock now neutralizes the previously-RAW prompt-facing fields
  (title, file_name, image caption/tableTitle/tableLabel, index warnings)
  (Vectors B/C, mitigation #2).
- The fence escaper is case-insensitive and evidence text is neutralized after
  glyph normalization, defusing lowercase forged sentinels and zero-width /
  homoglyph obfuscation (mitigation #3, INJ-3/12).
- Every derived evidence field (synopsis, adjacent context, table facts,
  memory cards, images, section path) is escaped in place so a forged
  close-then-reopen sentinel cannot straddle the real fence (Vector E,
  mitigation #5). Escaping in place rather than wrapping each field avoids the
  ~940 input-tokens/answer the per-field wrapper added, which tipped
  near-timeout strong-route answers over budget.
- Answer prompt-cache key bumped v17 -> v18 (cached instruction prefix
  changed). The longer instruction prefix now crosses OpenAI's prompt-cache
  minimum, so cached-input share rises from ~4% to ~40-80% and cost/answer is
  neutral-to-lower despite the added text.

Adds tests/rag-injection.test.ts (deterministic INJ-3/4/6/12 + neutralizer /
escaper regression guards). Answer-text-level INJ cases need a seeded-chunk
generation harness and are deferred.

Gates: verify:cheap green (1261 tests); eval:quality --rag-only grounded-
supported 0.9333 (= baseline); eval:rag grounded-supported at parity within
run-to-run noise; retrieval untouched. No retrieval RPC / DB / migration
changes; no source-governance metadata added to ranking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdSevAcjHqHt4ezZkWYbE7
@supabase

supabase Bot commented Jul 8, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review July 8, 2026 04:29
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@BigSimmo
BigSimmo enabled auto-merge July 8, 2026 04:29
@BigSimmo
BigSimmo merged commit 3860140 into main Jul 8, 2026
5 checks passed
@BigSimmo
BigSimmo deleted the claude/llm-pipeline-review-7wihci branch July 8, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants